/*
 * JaCoLiB
 * Copyright (C) 2008 Alessandro Serra
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package it.gashale.jacolib.shell.js;


import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import it.gashale.jacolib.core.EvalError;
import it.gashale.jacolib.core.JacolibError;
import it.gashale.jacolib.core.ParserError;
import it.gashale.jacolib.core.ProtocolError;
import it.gashale.jacolib.core.TargetError;
import it.gashale.jacolib.shell.LocalShell;
import it.gashale.jacolib.util.FileSystem;

import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;


public class JSShell extends LocalShell {
	public final static String initial_input_resource="it/gashale/jacolib/shell/js/jacolib-init.js";
	public final static String language_name="javascript";
	private Context js_cx;
	private Scriptable js_scope;
	
	
	
	public JSShell() throws JacolibError {
		super(language_name);
		js_cx = ContextFactory.getGlobal().enterContext();
		js_scope=js_cx.initStandardObjects();
		notify_new_console();
		_init(initial_input_resource);
	}
	public Object eval(String code) throws JacolibError {
		try {		
			String eval="jacolib_exception=null;";
			eval+=" try { "+code+" }";
			eval+=" catch(e) { ";
			eval+=" jacolib_exception=e ";
			eval+=" } ";
			Object obj=js_cx.evaluateString(js_scope, eval, "<eval:"+code+">", 1, null);
			Object exception=js_scope.get("jacolib_exception", js_scope);
			if(exception!=null && org.mozilla.javascript.ScriptableObject.class.isInstance(exception)) {
				org.mozilla.javascript.ScriptableObject ex=(org.mozilla.javascript.ScriptableObject)exception;
				throw new TargetError(ex.toString());
			}			
			return conv_result(obj);
		} catch(EvaluatorException e) {
			throw new ParserError(e.getMessage());
		} catch(EcmaError e) {
			throw new TargetError(e.getMessage());
		}
	}
	public Object source(String filename) throws JacolibError {
		try {
			Object obj=js_cx.evaluateReader(js_scope, new FileReader(filename), filename, 1, null);
			return conv_result(obj);
		} catch(EvaluatorException e) {
			throw new ParserError(e.getMessage());
		} catch(EcmaError e) {
			throw new TargetError(e.getMessage());
		} catch (FileNotFoundException e) {
			throw new EvalError(e.getMessage());
		} catch (IOException e) {
			throw new EvalError(e.getMessage());
		}
	}
	public Object get(String key) throws JacolibError {
		return conv_result(js_scope.get(key, js_scope));
	}
	public void set(String key, Object obj) throws EvalError {
		js_scope.put(key, js_scope, obj);
	}
	public void unset(String key) throws EvalError {
		js_scope.put(key, js_scope, null);
	}
	protected Object conv_result(Object obj) throws JacolibError {		
		if (obj == Scriptable.NOT_FOUND) {
		    throw new EvalError("variable not found.");
		} else if(obj==null) {
			
		} else if(obj.getClass().equals(NativeJavaObject.class)) {
			NativeJavaObject o=(NativeJavaObject)obj;
			obj=o.unwrap();
		} else if(obj.getClass().equals(Undefined.class)) {
			obj=null;
		} else if(obj.getClass().equals(NativeObject.class)) {
			NativeObject o=(NativeObject)obj;
			JSONObject jobj=new JSONObject();
			Object ids[]=o.getIds();
			for(int i=0; i<ids.length; ++i) {
				try {
					jobj.put(ids[i].toString(),o.get(ids[i].toString(), js_scope));
				} catch (JSONException e) {
					throw new JacolibError("Inpossible to convert a NativeObject into a JSON Object.");
				}
			}
			obj=jobj;
		}
		return obj;
	}
	public void quit() throws JacolibError {
	}
	public void finalize() {
		Context.exit();
	}
	protected void notify_new_console() throws EvalError {
		set("console",getConsole());
	}
	
	private void _init(String resource) throws ProtocolError {
		if(resource!=null) {
			InputStream in = FileSystem.class.getClassLoader().getResourceAsStream(resource);
			if(in==null)
				throw new ProtocolError("Impossible to initialize external process with resource "+resource+".");
			try {
				js_cx.evaluateReader(js_scope, new InputStreamReader(in), "<_init>", 1, null);
			} catch (IOException e) {
				throw new ProtocolError(e.getMessage());
			}
		}
	}
}
